#region license
# This file is part of Grawlix.
# 
# Grawlix is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.
# 
# Grawlix is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public
# License along with Grawlix.  If not, see
# <http://www.gnu.org/licenses/>.
#endregion

"""The default strategy for game management."""

namespace Grawlix.Client
import Grawlix.Meta
import Grawlix.Services
import System
import System.Threading

final class DefaultGameLoader(ServiceBase, IGameLoader):
"""A simple game loader that should fulfill most uses."""
	
	def constructor([required] gameFactory as IGameFactory):
	"""
	Initializes the new loader with default parameters.
	Param gameFactory: The factory used to create new Games.
	"""
		self("Default", gameFactory)
	
	def constructor(
		[required] localWorldName as string,
		[required] gameFactory as IGameFactory):
	"""
	Initializes the new loader.
	Param localWorldName: The name of the local world to load.
	Param gameFactory: The factory used to create new Games.
	"""
		_localWorldName = localWorldName
		_gameFactory = gameFactory
	
	[lock(_lockObj)]
	def LoadLocal():
	"""
	Starts loading the local game.
	Remarks: The local game is used to ask the player
		which server to connect to.
		
		It can also be used for testing purposes.
	"""
		try:
			if _loadingGame is not null or _currentGame is not null:
				if _currentGame is null:
					check = _loadingGame.Target
				else:
					check = _currentGame.Target
				if IsDefaultClient(check) or IsBuiltInClient(check):
					Quit()
					log Warn, "Request to load the local game when " + \
						"the local game is active. Quitting instead."
					return
			StartDefaultLoad()
		except exception:
			Quit()
			log Warn, "Quitting because the default load was " + \
				"unable to complete successfully.\n" + \
				exception
	
	[lock(_lockObj)]
	def Host(
		[required] worldName as string,
		port as int):
	"""
	Emulates a game locally for others to connect to.
	Param worldName: The name of world to host.
	Param port: The port to host on.
	"""
		if port <= 0 or port > 65535:
			raise ArgumentException(
				"Valid ports are between 1 and 65535, inclusive. " +
				"${port} was specified.",
				"port")
		raise NotImplementedException(
			"Loading a network game is not yet supported.")
	
	[lock(_lockObj)]
	def Connect([required] hostname as string, port as int):
	"""
	Starts loading a game over the network.
	Param hostname: The name of the host or IP address.
	Param port: The port on the host to connect to.
	"""
		if port <= 0 or port > 65535:
			raise ArgumentException(
				"Valid ports are between 1 and 65535, inclusive. " +
				"${port} was specified.",
				"port")
		raise NotImplementedException(
			"Loading a network game is not yet supported.")
	
	[lock(_lockObj)]
	def CancelLoad():
	"""
	Cancels the currently specified load.
	Does not quit or change the current game.
	"""
		CancelLoadNoLock()
	
	private def CancelLoadNoLock():
	"""
	Cancels the currently specified load.
	Does not quit or change the current game.
	"""
		try:
			return if _loadingGame is null
			proxy = _loadingGame
			_loadingGame = null
			proxy.Dispose()
		except exception:
			log Warn, "Hit an error during client disposal. " + \
				"A memory leak might result.\n" + \
				exception
	
	def Quit():
	"""
	Starts the process of exiting the game.
	Remarks: This will force a complete game exit.
		To get back to the local game, call LoadLocal.
	"""
		Thread.VolatileWrite(_hasQuit, 1)
	
	[lock(_lockObj)]
	def Update(elapsed as timespan) as Game:
	"""
	Update the game loader.
	Param elapsed: The amount of time since last update.
	Returns: The currently loaded game.
	Remarks:
		For front-end use only.
		
		Will cause errors if not called by the front end, as
		it may invalidate a game that is already in use.
	"""
		if _currentGame is null and _loadingGame is null:
			Quit()
			return null
		
		return if _loadingGame is null
		loading = _loadingGame.Target
		return unless loading.Ready
		
		proxy = _loadingGame
		_loadingGame = null
		
		if loading.Failure is null:
		# Successful, swap in
			log Info, "Loading of ${loading} succeeded."
			old = _currentGame
			_currentGame = proxy
			try:
				if old is not null:
					old.Dispose()
			except exception:
				log Warn, "Error while disposing of an unused Game.\n" + \
					exception
			return _currentGame.Target
		
		# Failure, try another
		log Warn, "Unable to load ${loading}: ${loading.Failure}"
		
		if IsBuiltInClient(loading):
		# Built-in failed: quit
			Quit()
			log Fatal, \
				"Unable to load even the built-in client. " + \
				"Perhaps some configuration settings are incorrect."
		elif IsDefaultClient(loading):
			StartBuiltInLoad()
		else:
			StartDefaultLoad()
		proxy.Dispose()
		
		if _currentGame is not null:
			return _currentGame.Target
		return null
	
	private def StartDefaultLoad():
	"""Start loading the default client."""
		try:
			configBuilder = ServiceConfigurationBuilder.FromFile(
				_localWorldName,
				"World/${_localWorldName}/" +
				"Client/Properties/ServiceConfiguration.xml")
			searchPaths = "World/${_localWorldName}/Client;Library/Client"
			StartLoad(
				_localWorldName,
				configBuilder.Configuration,
				searchPaths)
		except exception:
			log Warn, "Loading built-in client due to " + \
				"error with default client configuration:\n" + \
				exception
			StartBuiltInLoad()
	
	private def StartBuiltInLoad():
	"""Start loading the built-in client."""
		configBuilder = ServiceConfigurationBuilder.FromFile(
			_builtInWorldName,
			"Properties/BuiltInServiceConfiguration.xml")
		StartLoad(_builtInWorldName, configBuilder.Configuration, "")
	
	private def IsDefaultClient([required] game as Game):
	"""
	Check if a particular client is the default.
	"""
		return game.FriendlyName == "Default"
	
	private def IsBuiltInClient([required] game as Game):
	"""
	Check if a particular client is the default.
	"""
		return game.FriendlyName == "Built-In"
	
	private def StartLoad(
		[required] worldName as string,
		[required] config as ServiceConfiguration,
		[required] searchPaths as string):
	"""
	Start loading a new client.
	Param worldName: The name of the world to load.
	Param config: The configuration file.
	Param searchPaths: The semicolon-delimetered list of
		paths to allow binary files.
	"""
		log Info, "Starting load of ${worldName}."
		CancelLoadNoLock()
		_loadingGame = _gameFactory.Create(worldName, config, searchPaths)
	
	Loading:
	"""An indicator describing whether a game is being loaded."""
		[lock(_lockObj)]
		get:
			return _loadingGame is not null
	
	HasQuit:
	"""An indicator describing whether the game is shutting down."""
		get:
			return Thread.VolatileRead(_hasQuit) != 0
	
	private _hasQuit as int
	"""An indicator describing whether the game is shutting down."""
	
	private _lockObj = object()
	"""The object to synchronize with."""
	
	private _currentGame as ExternalProxy[of Game]
	"""The currently active game."""
	
	private _loadingGame as ExternalProxy[of Game]
	"""The game that is loading. It will become current when activated."""
	
	private _gameFactory as IGameFactory
	"""The factory used to create game objects."""
	
	private _localWorldName as string
	"""The name of the default world to load."""
	
	private static final _builtInWorldName = "Built-In"
	"""The name of the built in world."""
